#include"World.h"
#include"Shader.h"
#include"ResourceManger.h"

#include"thirdparty\glfw3.h"

namespace Craft
{
	namespace
	{
		const GLfloat BLOCK_FIELD_RANGE = 6.0f;
		const GLfloat EFFECTIVE_RANGE = 2.0f;
	}


	World::World(Camera& camera) :
		m_camera(camera)
	{
		init_chunks();
	}

	World::~World()
	{
		// release chunks
		for (auto& chunk : m_chunks)
		{
			if (chunk)
				delete chunk;
		}
	}

	/*
	@brief 初始化chunks
	*/
	void World::init_chunks()
	{
		
		for (int x = 0; x < 1; x++)
			for (int y = 0; y < 4; y++)
				for (int z = 0; z < 1; z++)
				{
					Block buildBlock(2);

					if (y > 0)
					{
						buildBlock.set_Id(0);
					}

					Chunk* chunk = build_default_chunk(buildBlock, glm::vec3(x,y,z));

					rends.insert(std::pair<Chunk*, ChunkRender>(chunk, std::move(ChunkRender(chunk))));

					m_chunks.push_back(chunk);
				}

	}


	void World::update(GLdouble detla_time)
	{
		render_update(m_camera.getPostion());
	}


	void World::render(Shader& shader)
	{
		glActiveTexture(GL_TEXTURE0);
		ResourceManger::get_texutre("block").bind();
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		for (auto& rend : rends)
			rend.second.render(shader);
	}


	/*
		@brief 渲染更新
		检测每一个chunk是否需要更新,如果需要,则chunk对应
		的chunkrender重新填充数据
	*/
	void World::render_update(const glm::vec3& position)
	{
		for (auto& chunk : m_chunks)
		{
			if (chunk && chunk->is_update())
			{
				GLdouble currtime = glfwGetTime();
				auto itr = rends.find(chunk);
				if (itr != rends.end())
				{
					itr->second.render_update();
				}
				std::cout << "render update time: " << glfwGetTime() - currtime << std::endl << std::endl;
				chunk->set_update(false);
			}
		}// end for


	}

	/*
		@brief 获取碰撞盒
		获取每个chunk和camera发生碰撞的碰撞盒
	*/
	std::vector<AABB> World::get_hitBoxes(const AABB& box)
	{
		std::vector<AABB> hitBoxes;
		for (auto& chunk : m_chunks)
		{
			chunk->get_hitBoxes(box, hitBoxes);
		}
		return hitBoxes;
	}
	

	void World::load_chunks_toFile()
	{
	}


	void World::save_chunks_fromFile()
	{

	}


	/*
		@brief 获取相机选择block
			   返回捕获的block信息
	*/
	Block World::get_current_block()
	{
		glm::vec3 origin = m_camera.getPostion();
		glm::vec3 dir    = m_camera.getDirection();

		currBlock.set_Id(0);

		Chunk* oldChunk = nullptr;
		hitInfo.tMin_ = BLOCK_FIELD_RANGE;
		hitInfo.hitDir_ = -1;
		
		for_range_glmVec3(glm::vec3(-1.0f), glm::vec3(2.0f), [&](const glm::vec3& pos)
		{
			
			glm::vec3 pointPos = pos * BLOCK_FIELD_RANGE + origin;
			Chunk* currChunk = get_chunk_byWorldPos(pointPos);

			// 避免计算重复的chunks
			if (currChunk && currChunk != oldChunk)		
			{
				Block catchBlock = currChunk->intersection(origin, dir, BLOCK_FIELD_RANGE, hitInfo);
				if (catchBlock.get_Id())
				{
					currBlock = catchBlock;
				}
				oldChunk = currChunk;
			}
		});
		

		return currBlock;
	}


	/*
		@brief 建立block

		如果准星已经捕获一个方块,currBlock不为空气块(id > 0)
		则在currBlock的指向的面创建一个block，这个block属性由
		build_block的状态指定
	*/
	void World::try_build_block(Block buildBlock)
	{
		if (currBlock.get_Id() && glm::distance(hitInfo.hitPos_, m_camera.getPostion()) > EFFECTIVE_RANGE)
		{
			glm::vec3 new_block_pos = currBlock.get_world_pos();
			GLfloat block_size = currBlock.get_size();

			switch (hitInfo.hitDir_)
			{
			case FACE_LEFT:
				new_block_pos.x -= block_size;
				break;

			case FACE_RIGHT:
				new_block_pos.x += block_size;
				break;

			case FACE_TOP:
				new_block_pos.y += block_size;
				break;

			case FACE_BOTTOM:
				new_block_pos.y -= block_size;
				break;

			case FACE_FRONT:
				new_block_pos.z -= block_size;
				break;

			case FACE_BACK:
				new_block_pos.z += block_size;
				break;
			default:
				return;
				break;
			}

			Chunk* currChunk = get_chunk_byWorldPos(new_block_pos);
			if (currChunk && currChunk->get_block_byWorldPos(new_block_pos).get_Id() == 0)
			{
				currChunk->set_update(true);
				currChunk->set_block_byWorldPos(buildBlock, new_block_pos,true);
			}

		}// end if
	}


	Block World::try_destory_block()
	{
		if (currBlock.get_Id() > 0)
		{
			Chunk* currChunk = currBlock.get_chunk();
			if (currChunk)
			{
				currChunk->destory_block(currBlock.get_pos(),true);
				currBlock.set_Id(0);
			}
		}

		return currBlock;
	}

	/*
		@brief 根据传入的世界坐标获取对应的chunk
			    对应chunk放置顺序为从小到大

				如果未找到对应chunk，返回nullptr，注意其他调用者需要检查
				返回chunk是否为nullptr
	*/
	Chunk* World::get_chunk_byWorldPos(const glm::vec3& pos)
	{
		int size = m_chunks.size();
		glm::vec3 chunk_pos = get_chunkPos(pos);

		int first = 0;
		int last = size - 1;

		while (first <= last)
		{
			int mid = (first + last) / 2;
			if (equal_glmVec3(m_chunks[mid]->get_pos(),chunk_pos) )
			{
				return m_chunks[mid];
			}
			else if (less_glmVec3(m_chunks[mid]->get_pos(), chunk_pos))
				first = mid + 1;
			else
				last =  mid - 1;
		}

		return nullptr;
	}


	glm::vec3 World::get_chunkPos(const glm::vec3& pos)
	{
		glm::vec3 chunk_pos(0.0f);

		chunk_pos.x =  GLint(pos.x >= 0 ? pos.x : pos.x - chunk_width + 1)  / chunk_width * chunk_width;
		chunk_pos.y =  GLint(pos.y >= 0 ? pos.y : pos.y - chunk_width + 1)  / chunk_width * chunk_width;
		chunk_pos.z =  GLint(pos.z >= 0 ? pos.z : pos.z - chunk_width + 1)  / chunk_width * chunk_width;

		return chunk_pos;
	}


	/*
		@brief 通过世界坐标系获取对应的block
	*/
	Block World::get_block_byWorldPos(const glm::vec3& pos)
	{
		Chunk* currChunk = get_chunk_byWorldPos(pos);

		if (currChunk)
		{
			return currChunk->get_block_byWorldPos(pos);
		}
		else
			return Block();
	}

	Chunk* World::build_default_chunk(Block block, const glm::vec3& pos)
	{
		glm::vec3 trans(0.0f);
		trans.x = pos.x * 16;
		trans.y = pos.y * 16;
		trans.z = pos.z * 16;

		GLuint index = pos.x * 16 * 16 + pos.y * 16 + pos.z;
		Chunk* chunk = new Chunk(index, this);
		chunk->set_pos(trans);

		for (size_t x = 0; x < 16; x++)
			for (size_t y = 0; y < 16; y++)
				for (size_t z = 0; z < 16; z++)
					chunk->set_block(block, glm::vec3(x, y, z));
		chunk->set_update(false);

		return chunk;
	}

}